/*
 * Copyright 2007 Rob Nielsen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.proxymatic.core;

import com.googlecode.proxymatic.core.util.ListUtil;

import java.io.Serializable;
import java.util.List;

/**
 * Buildtime context encapsulates information available to MethodHandlers at the time the proxy object is constructed.
 * It contains a list of targetInterfaces which are the interfaces implemented by the proxy object, a optional list of
 * implementationClasses which could be used by handlers to provide implementations for the methods in the
 * targetInterfaces.  There is no explicit correlation between the targetInterfaces and implementationClasses. It also
 * contains a flag to indicate if the associated RuntimeContext will have associated implementation objects for the
 * implementationClasses.  If this is false, handlers can only rely on constructors or static methods of the
 * implementationClasses.
 */
public class BuildtimeContext implements Serializable {
    private final List<Class> implementationClasses;
    private final List<Class> targetInterfaces;
    private final boolean implementationObjectsAvailable;

    /**
     * Constructs a new buildtime context.
     *
     * @param targetInterfaces               the interfaces to be implemented by the proxy object
     * @param implementationClasses          an array of Classes that can be used to provide implementation
     * @param implementationObjectsAvailable true if the runtime context will provide instances of each of the
     *                                       implementationClasses, false otherwise
     */
    public BuildtimeContext(Class[] targetInterfaces, Class[] implementationClasses, boolean implementationObjectsAvailable) {
        if (targetInterfaces == null || targetInterfaces.length == 0) {
            throw new IllegalArgumentException("At least one target interface is required");
        }
        for (int i = 0; i < targetInterfaces.length; i++) {
            Class targetInterface = targetInterfaces[i];
            if (!targetInterface.isInterface()) {
                throw new IllegalArgumentException(targetInterface + " must be an interface!");
            }
        }
        this.targetInterfaces = ListUtil.asProtectedList(targetInterfaces);
        this.implementationClasses = ListUtil.asProtectedList(implementationClasses);
        this.implementationObjectsAvailable = implementationObjectsAvailable;
    }

    /**
     * Constructs a new buildtime context with a single interface and implementation class.  This form is useful for
     * interceptor types of proxies which call through to a real object.
     *
     * @param targetInterface               the interface implemented by proxy object
     * @param implementationClass           a class that can be used to provide implementation for the interface
     * @param implementationObjectAvailable true if the runtime context will provide an instance of the
     *                                      implementationClasse, false otherwise
     */
    public BuildtimeContext(Class targetInterface, Class implementationClass, boolean implementationObjectAvailable) {
        this(new Class[]{targetInterface}, new Class[]{implementationClass}, implementationObjectAvailable);
    }

    /**
     * Constructs a new buildtime context with a single interface and no implementation class.  This form is for
     * handlers that can create an implementation for a method solely from it's signature.
     *
     * @param targetInterface the interface implemented by proxy object
     */
    public BuildtimeContext(Class targetInterface) {
        this(new Class[]{targetInterface}, new Class[]{}, false);
    }

    /**
     * Gets the first target interface
     *
     * @return the first target interface specified.
     */
    public Class getTargetInterface() {
        if (targetInterfaces.size() != 1) {
            throw new IllegalArgumentException("Expected 1 but found " + targetInterfaces.size() + " target interfaces");
        }
        return targetInterfaces.get(0);
    }

    /**
     * Convenience method for getting the single implementation class.  If multiple classes were provided, an exception
     * will be thrown.
     *
     * @return the implementation class
     */
    public Class getImplementationClass() {
        if (implementationClasses.size() != 1) {
            throw new IllegalArgumentException("Expected 1 but found " + implementationClasses.size() + " implementation classes");
        }
        return implementationClasses.get(0);
    }

    /**
     * Gets a read only list of the target interfaces supported by the proxy.
     *
     * @return a list of interfaces
     */
    public List<Class> getTargetInterfaces() {
        return targetInterfaces;
    }

    /**
     * Gets an array of the target interfaces supported by the proxy.
     *
     * @return an array of interfaces
     */
    public Class[] getTargetInterfacesArray() {
        return targetInterfaces.toArray(new Class[targetInterfaces.size()]);
    }

    /**
     * Gets a list of the implementation classes available to the proxy.  An empty list will be returned if there are no
     * implementation classes.
     *
     * @return a list of classes
     */
    public List<Class> getImplementationClasses() {
        return implementationClasses;
    }

    /**
     * Will instances of each of the implementationClasses be available in the associated RuntimeContext?
     *
     * @return true if they will be, false otherwise.
     */
    public boolean isImplementationAvailableAtRuntime() {
        return implementationObjectsAvailable;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        BuildtimeContext that = (BuildtimeContext) o;
        if (implementationObjectsAvailable != that.implementationObjectsAvailable) {
            return false;
        }
        if (!implementationClasses.equals(that.implementationClasses)) {
            return false;
        }
        return targetInterfaces.equals(that.targetInterfaces);
    }

    public int hashCode() {
        int result;
        result = implementationClasses.hashCode();
        result = 31 * result + targetInterfaces.hashCode();
        result = 31 * result + (implementationObjectsAvailable ? 1 : 0);
        return result;
    }
}
